package org.axed.user.client.axessory;

import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Element;

import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;

import org.axed.user.client.Anchor;
import org.axed.user.client.AxedArea;
import org.axed.user.client.AxedEvent;
import org.axed.user.client.AxedInternalError;
import org.axed.user.client.AxedInterval;
import org.axed.user.client.AxedListener;
import org.axed.user.client.AxedPointer;
import org.axed.user.client.DOM;
import org.axed.user.client.FindResult;
import org.axed.user.client.Selection;
import org.axed.user.client.InputBase;
import org.axed.user.client.InputCatcher;
import org.timepedia.exporter.client.Exportable;

/**
 * Handles UserInput events and sends out Document alteration events.
 */
public class VimInput extends InputBase implements AxedListener {
	public final static boolean DEBUG_COMMANDS = false;
	
	public final static int MODE_NORMAL      = 1;
	public final static int MODE_INSERT      = 2;
	public final static int MODE_VISUAL      = 3;
	public final static int MODE_VISUAL_LINE = 4;

	public final static int TABSTOP = 2;

	FindResult fr = new FindResult();

	/**
	 * if false movements up and down are document lines
	 */
	public boolean movevisual = true;

	/**
	 * The typical vimbar.
	 */
	public VimBar vimBar;

	/**
	 * The mode.
	 */
	int mode;


	/**
	 * In insert mode this is true when the atomicID has be
	 * used aleady. It will create a new ID whenever the cursor 
	 * moved and the id was used for a change already.
	 */
	boolean usedAtomicID = false;

	/**
	 * Internal clipboard.
	 */
	private class Clipboard {
		/**
		 * Text.
		 */
		String text = "";

		/**
		 * true if this is a linebased selection
		 */
		boolean lined;

		void set(AxedInterval iv, boolean lined) {
			text = axed.getText(iv);
			this.lined = lined;
		}
	};

	Clipboard clipboard = new Clipboard();

	/**
	 * TODO
	 */
	public class Command { // TODO about public
		/**
		 * A command being entered.
	 	 */
		char cmd;

		/**
		 * Some commands can have a flag. for example: 
		 * 'a' ... a word, sentence, etc.
		 * 'i' ... inner word, sentence.
		 * 'g' ... "go" can be flagged to any command.
		 */
		char flag;
		
		/**
		 * The desitionation like 'w', 's', or '_' (e.g. dd).
		 */
		int dst;

		/**
		 * Command on visual interval
		 */
		AxedInterval dInt;
		
		/**
		 * the (dInt) interval is to be treated as a VISUAL_LINE result.
		 */
		boolean lined;
		
		/**
		 * Count the commmand is to be executed
		 */
		int count0;
		int count1;

		/**
		 * In case of '/', what has been searched for.
		 */
		String str1;	

		/**
		 * The current command is on transit, meaning
		 * it well be deleted when 'finished' or 'canceled'.
		 *
		 * The last command e.g. will keep its data when
		 * finished/canceled
		 */
		boolean transit;

		/**
		 * Constructor
		 */
		public Command(AxedArea axed, boolean transit) {
			dInt = new AxedInterval(axed);
			this.transit = transit;
		}

		/**
		 * Returns the total number a command is to be done
		 */
    	public int getCount() {
			return (count0 == 0 ? 1 : count0) * (count1 == 0 ? 1 : count1);
		}
	
		/**
		 * Set the message to debug the commands as they are build.
		 */
		private void debug(boolean did, String what) {
			if (!DEBUG_COMMANDS || AxedArea.debugLabel == null) {
				return;
			}
			String s = did ? "did " : "do ";
			if (count0 != 0 && count1!= 0) {
				s += count0 + "x" + count1 + " times ";
			} else if (count0 != 0 && count1 == 0) {
				s += count0 + " times ";
			} else if (count0 == 0 && count1 != 0) {
				s += count1 + " times ";
			}
			switch (cmd) {	
			case 'c' : s += "change ";         break;
			case 'd' : s += "delete ";         break;
			case 'g' : s += "go ";             break;
			case 'y' : s += "yank ";           break;
			case ':' : s += "enter command ";  break;
			case '/' : s += "search ";         break;
			case '?' : s += "searchbackward "; break;
			case '>' : s += "intend right "; break;
			case '<' : s += "intend left "; break;
			}
			if (what == null) {
				what = "... ";
			} 
			s += what;
			s += " (flag=" + (flag != 0 ? flag : '0') + ")";
			AxedArea.debugLabel.setText(s);
		}

		/**
		 * Cancels the current command. 
		 * Resets all flags, counts, etc.
		 */
		public void cancel() {
			if (!transit) {
				return;
			}
			if (DEBUG_COMMANDS) {
				AxedArea.debugLabel.setText("Cancel!");
			}
			count0 = 0;
			count1 = 0;
			cmd = 0;
			flag = 0;
			dst = 0;
			lined = false;
			dInt.invalidize();
		}

		/**
		 * Copy this command from another
		 */
		public void set(Command c) {
			this.cmd    = c.cmd;
			this.flag   = c.flag;
			this.dst    = c.dst;
			this.dInt.set(c.dInt);
			this.lined  = c.lined;
			this.count0 = c.count0;
			this.count1 = c.count1;
			this.str1   = c.str1;
		}

		/**
		 * Finishes a command, resets all cmd-variables.
		 */
		public void finish(String what) {
			if (!transit) {
				return;
			}
			debug(true, what);
			cmd = 0;
			flag = 0;
			count0 = 0;
			count1 = 0;
			lined = false;
			dInt.invalidize();
		}
	};

	Command command;
	Command lastCommand; 

	/**
	 * The last find command.
	 */
	String lastFind = "";

	
	private AxedPointer pBuf;

	/**
	 * The interval buffer for all kind of commands.
	 * Since almost all vim commands take a cursor movement as argument,
	 * this buffer will hold the answer of the affected interval, as parameter
	 * for the command.
	 */
	private AxedInterval pInt;
	
	/**
	 * The position where selection is happening
	 */
	public AxedInterval sel;

	/**
	 * TODO
	 */
	private VimCommand cmdBar     = new VimCommand(this);

	/**
	 * ':' or '/' or '?' when entering a command.
	 */
	char cmdMode = 0;

	/**	
	 * Constructor.
	 */
	public VimInput(AxedArea axed, VimBar vimBar) {
		super(axed);
		this.vimBar = vimBar;
		mode = MODE_NORMAL;
		sel  = new AxedInterval(axed);
		pInt = new AxedInterval(axed);
		pBuf = new AxedPointer(axed);
		command = new Command(axed, true);
		lastCommand = new Command(axed, false);
	}


	/**
	 * Displays the change of the filesize just like vim does.
	 * @param l0 number of lines before the command was executed.
	 */
	public void displaySizeChange(int l0) {
		int d = axed.getLineCount() - l0;
		if (d < -2) {
			vimBar.showMessage((-d) + " fewer lines.", vimBar.MESSAGE_NORMAL);
			return;
		}
		if (d > 2) {
			vimBar.showMessage(d + " more lines.", vimBar.MESSAGE_NORMAL);
			return;
		}
		vimBar.showMessage("", vimBar.MESSAGE_NORMAL);
	}
	
	private void keyCtrlLeft() {
		cursor.movePrevWord(1, false, true);
		vimCursorMoved();
	}

	private void keyCtrlRight() {
		cursor.moveNextWord(1, false, true);
		vimCursorMoved();
	}

	/**
	 * Handles numbers in normal/visual mode
	 */
	private void digit(int keycode) {
		if (command.cmd != 0) {
			// this is the 2nd count specifier
			if (keycode == '0' && command.count0 == 0) {
				// this is a command not a number
				keyZero();
				return;
			} 
			command.count1 = command.count1*10 + (keycode - '0');
			command.debug(false, null);
			return;
		}
		if (keycode == '0' && command.count0 == 0) {
			// this is a command not a number
			keyZero();
			return;
		} 
		command.count0 = command.count0*10 + (keycode - '0');
		command.debug(false, null);
	}
	
	/**
	 * TODO
	 */
	private void getCommandInterval(Command c, AxedInterval p) {
		switch (c.dst) {
		case '_' : // interval = x lines.
			p.setLines(cursor.line, axed.confineLine(cursor.line + c.getCount() - 1), true);
			break;
		case -InputCatcher.KEY_END :
			p.set(cursor, cursor);
			p.p2.moveLineEnd((c.flag == 'g') ^ movevisual);
			break;
		case '$' : // until physical EOL
			p.set(cursor, cursor);
			p.p2.moveLineEnd(false);
			break;
		case -InputCatcher.KEY_POS1 :
			p.set(cursor, cursor);
			p.p1.moveLineStart((c.flag == 'g') ^ movevisual); 
			break;
		case '0' : // until physical SOL
			p.set(cursor, cursor);
			p.p1.moveLineStart(false); 
			break;
		case 'e' : // until word end
			p.set(cursor, cursor);
			p.p2.moveNextWord(c.getCount(), true, false);
			break;
		case 'E' : // until word end
			p.set(cursor, cursor);
			p.p2.moveNextWord(c.getCount(), false, false);
			break;
		case 'g' : // until count line
			p.p1.set(axed.confineLine(c.getCount() - 1), 0);
			p.p2.set(cursor);
			p.normalize();
			p.expandToLines();
			break;
		case 'G' : // until count line or end of document
			{
			p.p1.set(cursor);
			int co = c.getCount() - 1;
			int lc = axed.getLineCount() - 1;
			if (c.count0 == 0 || co > lc) {
				p.p2.line = lc;
			} else {
				p.p2.line = co;
			}
			p.p2.col = 0;
			p.normalize();
			p.expandToLines();
			break;
			}
		case 's' : // until sentence
			switch (c.flag) {
			case 0 :
				return;
			case 'i' :
				p.set(cursor, cursor);
				p.p1.moveSentence(-1, false);
				p.p2.moveSentence(1, true);
				break;
			case 'a' :
				p.set(cursor, cursor);
				p.p1.moveSentence(-1, false);
				p.p2.moveSentence(1, false);
				break;
			}
			break;
		case 't' : // until tag
			switch (c.flag) {
			case 0 : 
				return;
			case 'a' :
				{
				p.p1.set(cursor);
				String t = p.p1.movePrevTag(c.getCount());
				if (t == null) {
					c.cancel();
					return;
				}
				p.p2.set(p.p1);
				if (!p.p2.moveEndTag(t)) {
					c.cancel();
					return;
				}
				break;
				}
			case 'i' :
				{
				p.p1.set(cursor);
				String t = p.p1.movePrevTag(c.getCount());
				if (t == null) {
					c.cancel();
					return;
				}
				p.p2.set(p.p1);
				if (!p.p2.moveEndTag(t)) {
					c.cancel();
					return;
				}
				p.p1.moveToChar(1, '>', false);
				p.p1.moveRight(1, false);
				p.p2.moveToChar(-1, '<', false);
				break;
				}
			}
			break;
		case 'w' : // until begin of next word. 
			switch (c.flag) {
			case 0:
				p.set(cursor, cursor);
				p.p2.moveNextWord(command.getCount(), true, true);
				break;
			case 'a' :
				p.set(cursor, cursor);
				p.p1.moveThisWord(true, true);
				p.p2.moveNextWord(1, true, true);
				break;
			case 'i' :
				p.set(cursor, cursor);
				p.p1.moveThisWord(true, true);
				p.p2.moveThisWord(true, false);
				break;
			}
			break;
		case 'W' : // until begin of next word.
			switch (c.flag) {
			case 0 :
				p.set(cursor, cursor);
				p.p2.moveNextWord(command.getCount(), false, true);
				break;
			case 'a' :
				p.set(cursor, cursor);
				p.p1.moveThisWord(false, true);
				p.p2.moveNextWord(1, false, true);
				break;
			case 'i' :
				p.set(cursor, cursor);
				p.p1.moveThisWord(false, true);
				p.p2.moveThisWord(false, false);
				break;
			}
			break;
		case '/' : // until search result
			{
			boolean f = axed.match(c.str1, cursor, fr, false);
			if (!f) {
				vimBar.showMessage("Pattern not found:" + c.str1, vimBar.MESSAGE_VERY_RED);
				return;
			}
			p.p1.set(cursor);
			p.p2.line = fr.line;
			p.p2.col  = fr.col;
			if (fr.wrapped) {
				vimBar.showMessage("search hit BOTTOM, continuing at TOP", vimBar.MESSAGE_RED);
			} else {
				vimBar.showMessage("", vimBar.MESSAGE_NORMAL);
			}
			break;
			}
		}
	}

	/**
	 * TODO
	 */
	public void exec(Command c) {
		AxedInterval p = pInt; // TODO

		if (c.dInt.p1.line < 0) {
			// not set, create an interval
			getCommandInterval(c, pInt);
		} else {
			// interval already set, take this.
			if (c == command) {
				// normal command, take the intervall directly.
				pInt.set(c.dInt);
			} else {
				pInt.set(cursor, cursor);
				pInt.p2.moveBy(c.dInt);
			}
		}

		switch (c.cmd) {
		case 'c' : 
			{
				int l0 = axed.getLineCount();
				clipboard.set(p, c.lined);
				lastAtomicID = axed.createAtomicID();
				axed.removeText(lastAtomicID, p);
				displaySizeChange(l0);
				switchMode(MODE_INSERT, true);
				break;
			}
		case 'd' :
		case 'x' :
			{
				int l0 = axed.getLineCount();
				clipboard.set(p, c.lined);
				lastAtomicID = axed.createAtomicID();
				axed.removeText(lastAtomicID, p);
				displaySizeChange(l0);
				break;
			}
		case 'y' :
			{
				clipboard.set(p, c.lined);
				int co = p.p2.line - p.p1.line + 1;
				if (co > 2) {
					vimBar.showMessage(co + " lines yanked", vimBar.MESSAGE_NORMAL);
				}
				break;
			}
		case '<' :
			{
				lastAtomicID = axed.createAtomicID();
				int ap1 = 0; // ignore start line that is not really "selected".
				int ap2 = 0; // ignore end line that is not really "selected".
				if (p.p1.col == axed.getLineLength(p.p1.line)) {
					ap1 = 1; 
				}
				if (p.p2.col == 0) {
					ap2 = 1;
				}
				for (int i = p.p1.line + ap1; i <= p.p2.line - ap2; i++) {
					String s = axed.getText(i);
					int j;
					for (j = 0; j < TABSTOP; j++) {
						if (!Character.isSpace(s.charAt(j))) {
							break;
						}
					}
					if (j > 0) {
						axed.removeText(lastAtomicID, i, 0, j);
					}
				}
				int co = p.p2.line - p.p1.line + 1 - ap1 - ap2;
				if (co > 2) {
					vimBar.showMessage(c + " lines &lt;ed 1 time", vimBar.MESSAGE_NORMAL);
				}
				break;
			}
		case '>' :
			{
				String s = "";
				for (int j = 0;  j < TABSTOP; j++) {
					s += " ";
				}
				lastAtomicID = axed.createAtomicID();
				int ap1 = 0; // ignore start line that is not really "selected".
				int ap2 = 0; // ignore end line that is not really "selected".
				if (p.p1.col == axed.getLineLength(p.p1.line)) {
					ap1 = 1; 
				}
				if (p.p2.col == 0) {
					ap2 = 1;
				}
				for (int i = p.p1.line + ap1; i <= p.p2.line - ap2; i++) {
					axed.insertText(lastAtomicID, i, 0, s, null);
				}
				int co = p.p2.line - p.p1.line + 1 - ap1 - ap2;
				if (co > 2) {
					vimBar.showMessage(co + " lines &gt;ed 1 time", vimBar.MESSAGE_NORMAL);
				}
				break;
			}
		default :
			c.cancel();
			return;
		}

		if (c == command) {
			lastCommand.set(c);
		}
		c.finish("on interval");
	}

	/**
	 * Called after cursor move, to correct caret and visual areas.
	 */
	void vimCursorMoved() {
		fireCursorMoved();
		switch (mode) {
		case MODE_INSERT :
			if (usedAtomicID) {
				lastAtomicID = axed.createAtomicID();
				usedAtomicID = false;
			}
			break;
		case MODE_VISUAL :
			sel.p2.set(cursor);
			axed.unselect(); // TODO, this is only needed due to bug in ranges/selection.
			axed.setSelection(sel);
			break;
		case MODE_VISUAL_LINE :
			sel.p2.set(cursor);
			axed.unselect();
			pInt.set(sel);
			pInt.expandToLines();
			axed.setSelection(pInt);
			break;
		}
	}


	/**
	 * Arrow key
	 */
	public void keyLeft() {
		cursor.moveLeft(command.getCount(), false);
		vimCursorMoved();
		command.finish("key left");
	}

	private void keyDown() {
		cursor.moveDown(command.getCount(), (command.flag == 'g') ^ movevisual);
		vimCursorMoved();
		command.finish("key down");
	}

	public void vimFind(String text) {
		lastFind = text;
		boolean f = axed.match(text, cursor, fr, false);
		if (!f) {
			vimBar.showMessage("Pattern not found:" + text, vimBar.MESSAGE_VERY_RED);
			return;
		}
		//TODO .set()
		cursor.line = fr.line;
		cursor.col = fr.col;
		if (fr.wrapped) {
			vimBar.showMessage("search hit BOTTOM, continuing at TOP", vimBar.MESSAGE_RED);
		} else {
			vimBar.showMessage("", vimBar.MESSAGE_NORMAL);
		}
		return;
	}
	
	/**
	 * backspace-key
	 */
	private void keyBackspace() {
		switch (mode) {
		case MODE_NORMAL :
		case MODE_VISUAL :
		case MODE_VISUAL_LINE :
			keyLeft();
			return;
		}
		assert mode == MODE_INSERT;

		usedAtomicID = true;
		if (cursor.isPos1()) {
			return;
		}
		if (cursor.col >= 1) {
			cursor.col--;
			axed.removeChar(lastAtomicID, cursor, InputCatcher.KEY_BACKSPACE);
		} else { 
			assert cursor.line > 0 && cursor.col == 0;
			cursor.col = axed.getLineLength(cursor.line - 1);
			cursor.line--;
			axed.joinLine(lastAtomicID, cursor.line);
		}
		fireCursorMoved();
		command.finish("backspace");
	}

	/**
	 * del-key
	 */
	private void keyDel() {
		switch (mode) {
		case MODE_NORMAL :
		case MODE_VISUAL :
		case MODE_VISUAL_LINE :
			keyLeft();
			return;
		}
		assert mode == MODE_INSERT;

		usedAtomicID = true;
		if (cursor.isEol()) {
			if (cursor.isLastLine()) {
				return;
			}
			if (mode == MODE_NORMAL) {
				lastAtomicID = axed.createAtomicID();
			}
			axed.joinLine(lastAtomicID, cursor.line);
			return;
		}
		if (mode == MODE_NORMAL) {
			lastAtomicID = axed.createAtomicID();
		}
		axed.removeChar(lastAtomicID, cursor, InputCatcher.KEY_DEL);
		//vimCursorMoved();
		command.finish("del char");
	}

	/**
	 * Carridge return key
	 */
	private void keyReturn() {
		if (mode == MODE_INSERT) {
			axed.splitLine(lastAtomicID, cursor);
			cursor.col = 0;
			cursor.line++;	
			cursor.retainLeft = -1;
			fireCursorMoved();
		} else {
			cursor.moveDown(command.getCount(), (command.flag == 'g') ^ movevisual);
			// TODO g+return does something else
			vimCursorMoved();
			command.finish("return down");
		}
	}

	/**
	 * Starts entering a colon command.
	 */
	private void toCommandLine(char ch) {
		cmdMode = ch;
		cmdBar.resetCmd("" + ch);
	}

	/**
	 * Escape-Key
	 */
	private void keyESC() {
		if (command.cmd != 0) {
			command.cancel();
			return;
		} 

		switch (mode) {
		case MODE_VISUAL :
		case MODE_VISUAL_LINE :
			switchMode(MODE_NORMAL);
			sel.invalidize();
			axed.unselect();
			return;
		case MODE_INSERT :
			switchMode(MODE_NORMAL);
			return;
		}
	}

	/**
	 * '0'-key.
	 */
	private void keyZero() {
		switch (command.cmd) {
		case 0 :
			cursor.moveLineStart(false);
			vimCursorMoved();
			command.finish("go line start");
			break;
		case 'g' :
			cursor.moveLineStart(true);
			vimCursorMoved();
			command.finish("go line start");
			break;
		case 'c' :
		case 'd' :
		case 'y' :
		case '<' :
		case '>' :
			command.dst = '0';
			command.lined = false;
			exec(command);
			break;
		default :
			command.cancel();
		}
	}


	/**
	 * Up arrow.
	 */
	private void keyUp() {
		cursor.moveUp(command.getCount(), (command.flag == 'g') ^ movevisual);
		vimCursorMoved();
		command.finish("key up");
	}

	/**
	 * Right arrow.
	 */
	private void keyRight() {
		cursor.moveRight(command.getCount(), false);
		vimCursorMoved();
		command.finish("key right");
	}

	/**
	 * Page down.
	 */
	private void keyPageDown() {
		int count = command.getCount();
		cursor.movePages(count);
		axed.setScrollTop(axed.getScrollTop() + count * axed.getClientHeight());
		vimCursorMoved();
		command.finish("key page down");
	}

	/**
	 * Page up.
	 */
	private void keyPageUp() {
		int count = command.getCount();
		cursor.movePages(-count);
		axed.setScrollTop(axed.getScrollTop() - count * axed.getClientHeight());
		vimCursorMoved();
		command.finish("key page  up");
	}
	
	/**
	 * '$'-key.
	 */
	private void keyDollar() {
		switch (command.cmd) {
		case 0 :
			cursor.moveLineEnd(false);
			vimCursorMoved();
			command.finish("go line end");
			break;
		case 'g' :
			cursor.moveLineEnd(true);
			vimCursorMoved();
			command.finish("go line end");
			break;
		case 'c' :
		case 'd' :
		case 'y' :
		case '<' :
		case '>' :
			command.dst = '$';
			command.lined = false;
			exec(command);
			break;
		default :
			command.cancel();
		}
	}

	/**
	 * '.'-key
	 */
	private void keyDot() {
		if (mode != MODE_NORMAL) {
			return;
		}
		exec(lastCommand);
	}

	/**
	 * End key.
	 */
	private void keyEnd() {
		switch (command.cmd) {
		case 0 :
			cursor.moveLineEnd(movevisual);
			vimCursorMoved();
			command.finish("go line end");
			break;
		case 'g' :
			cursor.moveLineEnd(!movevisual);
			vimCursorMoved();
			command.finish("go visual line end");
			break;
		case 'c' :
		case 'd' :
		case 'y' :
		case '<' :
		case '>' :
			command.dst = -InputCatcher.KEY_END;
			command.lined = false;
			exec(command);
			break;
		default :
			command.cancel();
		}
	}

	/**
	 * Pos1 key
	 */
	private void keyPos1() {
		switch (command.cmd) {
		case 0 :
			cursor.moveLineStart(movevisual);
			vimCursorMoved();
			command.finish("go line start");
			break;
		case 'g' :
			cursor.moveLineStart(!movevisual);
			vimCursorMoved();
			command.finish("go visual line start");
			break;
		case 'c' :
		case 'd' :
		case 'y' :
		case '<' :
		case '>' :
			command.dst = -InputCatcher.KEY_POS1;
			command.lined = false;
			exec(command);
			break;
		default :
			command.cancel();
		}
	}


	private void redo() {
		axed.redo();
		vimBar.showMessage("1 change, ", vimBar.MESSAGE_NORMAL);
		command.finish("redo");
		return;
	}

	/**
	 * 'a'-key
	 *
	 * switches to insert mode (appending move 1 to right first), or
	 * specifies an 'a' flag.
	 */
	public void keyLowerA() {
		switch (mode) {
		case MODE_VISUAL :
		case MODE_VISUAL_LINE :
			switch (command.flag) {
			case 0:
				command.flag = 'a';
				command.debug(false, null);
				return;
			default:
				command.cancel();
				return;
			}
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0:
				cursor.moveRight(1, false);
				vimCursorMoved();
				switchMode(MODE_INSERT);
				return;
			case 'c':
			case 'd':
			case 'y':
			case '<' :
			case '>' :
				// this is an 'a' specifier.
				switch (command.flag) {
				case 0:
					command.flag = 'a';
					command.debug(false, null);
					return;
				default :
					command.cancel();
					return;
				}
			default :
				command.cancel();
				return;
			}
		default : 
			throw new AxedInternalError("unkown mode in keyLowerI().");
		}
	}

	/**
	 * 'A'-key
	 */
	public void keyUpperA() {
		if (mode != MODE_NORMAL) {
			command.cancel();
			return;
		}
		switch (command.cmd) {
		case 0:
			cursor.moveLineEnd(false);
			vimCursorMoved();
			switchMode(MODE_INSERT);
			return;
		default :
			command.cancel();
			return;
		}
	}

	/**
	 * 'c'-key
	 */
	public void keyLowerC() {
		if (mode != MODE_NORMAL) {
			command.cancel();
			return;
		}
		switch (command.cmd) {
		case 0:
			command.cmd = 'c';
			command.debug(false, null);
			break;
		case 'c' :
			// change x lines from cursor;
			command.dst = '_';
			command.lined = true;
			exec(command);
			return;
		default :
			command.cancel();
			return;
		}
	}

	/**
	 * 'd'-key.
	 */
	private void keyLowerD() {
		switch (mode) {
		case MODE_VISUAL_LINE :
			sel.expandToLines();
			// fall through
		case MODE_VISUAL : 
			sel.normalize();
			command.cmd = 'd';
			command.dst = '-';
			command.lined = mode == MODE_VISUAL_LINE;
			command.dInt.set(sel);
			axed.unselect();
			cursor.set(sel.p1);
			fireCursorMoved();
			exec(command);
			switchMode(MODE_NORMAL);
			return;
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0:
				command.cmd = 'd';
				break;
			case 'd' :
				command.dst = '_';
				command.lined = true;
				exec(command);
				return;
			default : 
				command.cancel();
			}
			return;
		}
	}

	/**
	 * 'D'-key.
	 */
	private void keyUpperD() {
		switch (mode) {
		case MODE_VISUAL_LINE :
			sel.expandToLines();
			// fall through
		case MODE_VISUAL :
			sel.normalize();
			command.cmd = 'd';
			command.dst = '-';
			command.lined = mode == MODE_VISUAL_LINE;
			command.dInt.set(sel);
			axed.unselect();
			cursor.set(sel.p1);
			fireCursorMoved();
			exec(command);
			switchMode(MODE_NORMAL);
			return;
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0:
				command.cmd = 'd';
				command.dst = '$';
				command.lined = false;
				exec(command);
				return;
			default :
				command.cancel();
			}
			return;
		}
	}


	/**
	 * Moves to next end of word, splitting char classes.
	 */
	private void keyLowerE() {
		switch (command.cmd) {
		case 0 :
			cursor.moveNextWord(command.getCount(), true, false);
			vimCursorMoved();
			command.finish("move word");
			break;
		case 'c' :
		case 'd' :
		case 'y' :
		case '<' :
		case '>' :
			command.dst = 'e';
			command.lined = false;
			exec(command);
			break;
		default :
			command.cancel();
		}
	}

	/**
	 * Moves to next end of word, ignoring char classes.
	 */
	private void keyUpperE() {
		switch (command.cmd) {
		case 0 :
			cursor.moveNextWord(command.getCount(), false, false);
			vimCursorMoved();
			command.finish("move word");
			break;
		case 'c' :
		case 'd' :
		case 'y' :
		case '<' :
		case '>' :
			command.dst = 'E';
			command.lined = false;
			exec(command);
			break;
		default :
			command.cancel();
		}
	}

	/**
	 * The 'g'-key.
	 */
	private void keyLowerG() {
		// behavior in visual or normal mode equal.
		switch (command.cmd) {
		case 0:
			command.cmd = 'g';
			command.flag = 'g';
			command.debug(false, null);
			break;
		case 'g' :
			cursor.line = command.getCount() - 1;
			int lc = axed.getLineCount() - 1;
			if (cursor.line > lc) {
				cursor.line = lc;
			}
			cursor.col = 0;
			vimCursorMoved();
			command.finish("go go");
			break;
		default : 
			switch (command.flag) {
			case 0 :
				command.flag = 'g';
				command.debug(false, null);
				return;
			case 'g' :
				command.dst = 'g';
				command.lined = true;
				exec(command);
				return;
			default :
				command.cancel();
				return;
			}
		}
	}

	/**
	 * The 'G'-key.
	 */
	private void keyUpperG() {
		switch (command.cmd) {
		case 0:
			{
				int c = command.getCount() - 1;
				int lc = axed.getLineCount() - 1;
				if (command.count0 == 0 || c > lc) {
					cursor.line = lc;
				} else {
					cursor.line = c;
				}
				cursor.col = 0;
				vimCursorMoved();
				command.finish("go end of file");
				return;
			}
		default :
			{
				command.dst = 'G';
				command.lined = true;
				exec(command);
				return;
			}
		}
	}


	/**
	 * 'I'-key
	 */
	public void keyUpperI() {
		cursor.col = 0;
		if (!cursor.isEol()) { // do nothing if this line is zero length
			while (!cursor.isEol() && Character.isSpace(axed.getChar(cursor))) {
				cursor.col++;
				if (cursor.isEol()) {
					break;
				}
			}
		}
		vimCursorMoved();
		switchMode(MODE_INSERT);
	}

	/**
	 * 'i'-key.
	 *
	 * switches to insert mode, or
	 * specifies 'inner' flag.
	 */
	public void keyLowerI() {
		switch (mode) {
		case MODE_VISUAL_LINE :
		case MODE_VISUAL :
			switch (command.flag) {
			case 0:
				command.flag = 'i';
				command.debug(false, null);
				return;
			default:
				command.cancel();
				return;
			}
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0:
				switchMode(MODE_INSERT);
				return;
			case 'c':
			case 'd':
			case 'y':
			case '<' :
			case '>' :
				// this is an 'inner' specifier.
				switch (command.flag) {
				case 0:
					command.flag = 'i';
					command.debug(false, null);
					return;
				default :
					command.cancel();
					return;
				}
			default :
				command.cancel();
				return;
			}
		default :
			throw new AxedInternalError("unkown mode in keyLowerI().");
		}
	}

	/**
	 * 'J'-key.
	 *
	 * Joins line.
	 */
	private void keyUpperJ() {
		lastAtomicID = axed.createAtomicID();
		int count = command.getCount();
		while (count-- > 0 && cursor.line + 1 < axed.getLineCount()) {
			// add a space if terminating with and starting with non space char.
			int ll1 = axed.getLineLength(cursor.line);
			int ll2 = axed.getLineLength(cursor.line+1);
			if (ll1 > 0 && ll2 > 0) {
				if (!Character.isSpace(axed.getText(cursor.line).charAt(ll1 - 1)) &&
					!Character.isSpace(axed.getText(cursor.line + 1).charAt(0))) {
					axed.insertText(lastAtomicID, cursor.line, ll1, " ", null);
				}
			}
			// join the line
			axed.joinLine(lastAtomicID, cursor.line);
		}
	}

	/**
	 * 'n'-key.
	 *
	 * finds next.
	 */
	private void keyLowerN() {
		vimFind(lastFind);
		vimCursorMoved();
	}

	/**
	 * 'o'-key.
	 *
	 * switches to other in visual mode.
	 */
	private void keyLowerO() {
		switch (mode) {
		case MODE_VISUAL_LINE :
		case MODE_VISUAL :
			cursor.set(sel.p1);
			sel.p1.set(sel.p2);
			vimCursorMoved();
			return;
		default :
			command.cancel();
			return;
		}
	}


	/**
	 * 'p'-key.
	 *
	 * pastes after cursor.
	 */
	private void keyLowerP() {
		int l0 = axed.getLineCount();
		if (mode == MODE_VISUAL) {
			// TODO
			return;
		}

		lastAtomicID = axed.createAtomicID();
		if (clipboard.lined) {
			cursor.moveLineEnd(false);
			cursor.moveRight(1, true);
			axed.insertText(lastAtomicID, cursor, clipboard.text, pBuf);
		} else {
			axed.insertText(lastAtomicID, cursor, clipboard.text, pBuf);
			cursor.set(pBuf);
		}
		displaySizeChange(l0);
		fireCursorMoved();
		command.finish("paste");
	}

	/**
	 * 'P'-key.
	 *
	 * pastes before cursor.
	 */
	private void keyUpperP() {
		int l0 = axed.getLineCount();
		if (mode == MODE_VISUAL) {
			// TODO
			return;
		}

		lastAtomicID = axed.createAtomicID();
		if (clipboard.lined) {
			cursor.moveLineStart(false);
			axed.insertText(lastAtomicID, cursor, clipboard.text, pBuf);
		} else {
			axed.insertText(lastAtomicID, cursor, clipboard.text, pBuf);
		}
		cursor.set(pBuf);
		displaySizeChange(l0);
		fireCursorMoved();
		command.finish("paste");
	}

	/**
	 * 's'-key.
	 *
	 * applies command to inner/a sentence.
	 */
	private void keyLowerS() {
		switch (mode) {
		case MODE_VISUAL_LINE :
		case MODE_VISUAL :
			// move visual next word, or inner word
			switch (command.cmd) {
			case 0 :
				switch (command.flag) {
				case 0 :
					// TODO vim actually behaves differently
					cursor.moveSentence(command.getCount(), false);
					vimCursorMoved();
					command.finish("move sentence");
					return;
				case 'a' : 
					if (sel.isZero()) {
						sel.p1.moveSentence(-1, false);
					}
					cursor.moveSentence(1, true);
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					vimCursorMoved();
					command.finish("select a sentence");
					return;
				case 'i':
					if (sel.isZero()) {
						sel.p1.moveSentence(-1, false);
					}
					cursor.moveSentence(1, false);
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					vimCursorMoved();
					command.finish("select inner sentence");
					return;
				default :
					command.cancel();
					return;
				}
			default :
				command.cancel();
				return;
			}
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0 :
				// TODO
				command.cancel();
				return;
			case 'c':
			case 'd' :
			case 'y' :
			case '<' :
			case '>' :
				switch (command.flag) {
				case 0:
					command.cancel();
					return;
				case 'a':
					if (command.getCount() != 1) {
						command.cancel();
						return;
					}
					command.dst = 's';
					command.lined = false;
					exec(command);
					return;
				case 'i':
					if (command.getCount() != 1) {
						command.cancel();
						return;
					}
					command.dst = 's';
					command.lined = false;
					exec(command);
					return;
				default :
					return;
				}
			default :
				command.cancel();
				return;
			}
		default :
			throw new AxedInternalError("unkown mode in keyLowerS().");
		}
	}

	/**
	 * 't'-key.
	 *
	 * applies command to inner/a tag.
	 */
	private void keyLowerT() {
		switch (mode) {
		case MODE_VISUAL_LINE :
		case MODE_VISUAL :
			// move visual next word, or inner word
			switch (command.cmd) {
			case 0 :
				switch (command.flag) {
				case 0 :
					command.cancel();
					return;
				case 'a' : 
				{
					pInt.p1.set(cursor);
					String t = pInt.p1.movePrevTag(command.getCount());
					if (t == null) {
						command.cancel();
						return;
					}
					pInt.p2.set(pInt.p1);
					if (!pInt.p2.moveEndTag(t)) {
						command.cancel();
						return;
					}

					if (sel.isZero()) {
						sel.p1.set(pInt.p1);
					}
					cursor.set(pInt.p2);
					vimCursorMoved();
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					command.finish("select a tag");
					return;
				}
				case 'i':
				{
					pInt.p1.set(cursor);
					String t = pInt.p1.movePrevTag(command.getCount());
					if (t == null) {
						command.cancel();
						return;
					}
					pInt.p2.set(pInt.p1);
					if (!pInt.p2.moveEndTag(t)) {
						command.cancel();
						return;
					}
					pInt.p1.moveToChar(1, '>', false);
					pInt.p1.moveRight(1, false);
					pInt.p2.moveToChar(-1, '<', false);

					if (sel.isZero()) {
						sel.p1.set(pInt.p1);
					}
					cursor.set(pInt.p2);
					vimCursorMoved();
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					command.finish("select inner tag");
					return;
				}
				default :
					command.cancel();
					return;
				}
			default :
				command.cancel();
				return;
			}
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0 :
				// TODO
				command.cancel();
				return;
			case 'c':
			case 'd' :
			case 'y' :
			case '<' :
			case '>' :
				switch (command.flag) {
				case 0:
					command.cancel();
					return;
				case 'a':
				case 'i':
				{
					if (command.getCount() != 1) {
						command.cancel();
						return;
					}
					command.dst = 't';
					command.lined = false;
					exec(command);
					return;
				}
				default :
					return;
				}
			default :
				command.cancel();
				return;
			}
		default :
			throw new AxedInternalError("unkown mode in keyLowerT().");
		}
	}

	/**
	 * 'u'-key.
	 */
	private void keyLowerU() {
		axed.undo();
		vimBar.showMessage("1 change", vimBar.MESSAGE_NORMAL);
		command.finish("undo");
	}

	/**
	 * 'U'-key.
	 */
	private void keyUpperU() {
		axed.undo();
		//TODO it should be slightly different than LowerU
		vimBar.showMessage("1 change", vimBar.MESSAGE_NORMAL);
		command.finish("undo");
	}

	/**
	 * 'v'-key.
	 */
	private void keyLowerV() { 
		switchMode(MODE_VISUAL);
		sel.set(cursor, cursor);
		axed.setSelection(sel);
	}

	/**
	 * 'V'-key.
	 */
	private void keyUpperV() { 
		switchMode(MODE_VISUAL_LINE);
		sel.set(cursor, cursor);
		axed.setSelection(sel);
		vimCursorMoved();
	}


	/**
	 * 'w'-key.
	 *
	 * moves to next word, or
	 * applies command to next word, or
	 * applies command to inner word, or
	 * applies command to a word, a
	 * changes visual to inner or a word.
	 */
	private void keyLowerW() {
		switch (mode) {
		case MODE_VISUAL :
		case MODE_VISUAL_LINE :
			// move visual next word, or inner word
			switch (command.cmd) {
			case 0 :
				switch (command.flag) {
				case 0 :
					cursor.moveNextWord(command.getCount(), true, true);
					vimCursorMoved();
					command.finish("move word");
					return;
				case 'a' : 
					if (sel.isZero()) {
						sel.p1.moveThisWord(true, true);
					}
					cursor.moveNextWord(1, true, true);
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					vimCursorMoved();
					command.finish("select a word");
					return;
				case 'i':
					if (sel.isZero()) {
						sel.p1.moveThisWord(true, true);
					}
					cursor.moveThisWord(true, false);
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					vimCursorMoved();
					command.finish("select inner word");
					return;
				default :
					command.cancel();
					return;

				}
			default :
				command.cancel();
				return;
			}
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0 :
				cursor.moveNextWord(command.getCount(), true, true);
				vimCursorMoved();
				command.finish("move word");
				return;
			case 'c':
			case 'd' :
			case 'y' :
			case '<' :
			case '>' :
				switch (command.flag) {
				case 0:
					command.dst = 'w';
					command.lined = false;
					exec(command);
					return;
				case 'a':
				case 'i':
					if (command.getCount() != 1) {
						command.cancel();
						return;
					}
					command.dst = 'w';
					command.lined = false;
					exec(command);
					return;
				default :
					return;
				}
			default :
				command.cancel();
				return;
			}
		default :
			throw new AxedInternalError("unkown mode in keyLowerI().");
		}
	}

	/**
	 * 'W'-key.
	 *
	 * moves to next word, or
	 * applies command to next word, or
	 * applies command to inner word, or
	 * applies command to a word, a
	 * changes visual to inner or a word.
	 */
	private void keyUpperW() {
		switch (mode) {
		case MODE_VISUAL :
		case MODE_VISUAL_LINE :
			// move visual next word, or inner word
			switch (command.cmd) {
			case 0 :
				switch (command.flag) {
				case 0 :
					cursor.moveNextWord(command.getCount(), false, true);
					vimCursorMoved();
					command.finish("move word");
					return;
				case 'a' : 
					if (sel.isZero()) {
						sel.p1.moveThisWord(false, true);
					}
					cursor.moveNextWord(1, false, true);
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					vimCursorMoved();
					command.finish("select a word");
					return;
				case 'i':
					if (sel.isZero()) {
						sel.p1.moveThisWord(false, true);
					}
					if (mode == MODE_VISUAL_LINE) {
						switchMode(MODE_VISUAL);
					}
					cursor.moveThisWord(false, false);
					vimCursorMoved();
					command.finish("select inner word");
					return;
				default :
					command.cancel();
					return;

				}
			default :
				command.cancel();
				return;
			}
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0 :
				cursor.moveNextWord(command.getCount(), false, true);
				vimCursorMoved();
				command.finish("move word");
				return;
			case 'c':
			case 'd' :
			case 'y' :
			case '<' :
			case '>' :
				switch (command.flag) {
				case 0:
					command.dst = 'W';
					command.lined = false;
					exec(command);
					return;
				case 'a':
				case 'i':
					if (command.getCount() != 1) {
						command.cancel();
						return;
					}
					command.dst = 'W';
					command.lined = false;
					exec(command);
					return;
				default :
					return;
				}
			default :
				command.cancel();
				return;
			}
		default :
			throw new AxedInternalError("unkown mode in keyLowerI().");
		}
	}

	/**
	 * 'x'-key.
	 */
	private void keyLowerX() {
		switch (mode) {
		case MODE_VISUAL_LINE : 
			sel.expandToLines();
			// fall-through
		case MODE_VISUAL : 
			sel.normalize();
			command.cmd = 'x';
			command.dst = '-';
			command.lined = mode == MODE_VISUAL_LINE;
			command.dInt.set(sel);
			axed.unselect();
			cursor.set(sel.p1);
			fireCursorMoved();
			exec(command);
			switchMode(MODE_NORMAL);
			return;
		case MODE_NORMAL :
			keyDel();
			return;
		}
	}

	/**
	 * 'X'-key.
	 */
	private void keyUpperX() {
		switch (mode) {
		case MODE_VISUAL_LINE :
			sel.expandToLines();
			// fall-through
		case MODE_VISUAL : 
			sel.normalize();
			command.cmd = 'x';
			command.dst = '-';
			command.lined = mode == MODE_VISUAL_LINE;
			command.dInt.set(sel);
			axed.unselect();
			cursor.set(sel.p1);
			fireCursorMoved();
			exec(command);
			switchMode(MODE_NORMAL);
			return;
		case MODE_NORMAL :
			keyBackspace();
			return;
		}
	}

	/**
	 * 'y'-key.
	 */
	private void keyLowerY() {
		switch (mode) {
		case MODE_VISUAL_LINE : 
			sel.expandToLines();
			// fall-through
		case MODE_VISUAL : 
			sel.normalize();
			command.cmd = 'y';
			command.dst = '-';
			command.lined = mode == MODE_VISUAL_LINE;
			command.dInt.set(sel);
			axed.unselect();
			cursor.set(sel.p1);
			fireCursorMoved();
			exec(command);
			switchMode(MODE_NORMAL);
			return;
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0 : 
				command.cmd = 'y';
				command.debug(false, null);
				break;
			case 'y' :
				command.dst = '_';
				command.lined = true;
				exec(command);
				break;
			default : 
				command.cancel();
				break;
			}
			return;
		}
	}

	/**
	 * '&gt;'-key.
	 */
	private void keyGreaterThan() {
		switch (mode) {
		case MODE_VISUAL_LINE : 
			sel.expandToLines();
			// fall-through
		case MODE_VISUAL : 
			sel.normalize();
			command.cmd = '>';
			command.dst = '-';
			command.lined = mode == MODE_VISUAL_LINE;
			command.dInt.set(sel);
			axed.unselect();
			exec(command);
			switchMode(MODE_NORMAL);
			return;
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0 : 
				command.cmd = '>';
				command.debug(false, null);
				break;
			case '>' :
				command.dst = '_';
				command.lined = true;
				exec(command);
				break;
			default : 
				command.cancel();
				break;
			}
			return;
		}
	}

	/**
	 * '&lt;'-key.
	 */
	private void keyLesserThan() {
		switch (mode) {
		case MODE_VISUAL_LINE : 
			sel.expandToLines();
			// fall-through
		case MODE_VISUAL : 
			sel.normalize();
			command.cmd = '<';
			command.dst = '-';
			command.lined = mode == MODE_VISUAL_LINE;
			command.dInt.set(sel);
			axed.unselect();
			exec(command);
			switchMode(MODE_NORMAL);
			axed.unselect();
			return;
		case MODE_NORMAL :
			switch (command.cmd) {
			case 0 : 
				command.cmd = '<';
				command.debug(false, null);
				break;
			case '<' :
				command.dst = '_';
				command.lined = true;
				exec(command);
				break;
			default : 
				command.cancel();
				break;
			}
			return;
		}
	}
	
	/**
	 * Tabulator key
	 */
	private void keyTab() {
		if (mode != MODE_INSERT) {
			return;
		}
		for (int i = 0; i < TABSTOP; i++) {
			axed.insertChar(lastAtomicID, cursor, ' ');
			cursor.col++;
			cursor.retainLeft = -1;
			axed.moveCursor(cursor, AxedEvent.MOVETYPE_FOLLOWUP);
			usedAtomicID = true;
		}
	}

	/**
	 * switches the mode, but keeps the atomicId
	 * if keepAtomic == true.
	 */
	private void switchMode(int mode, boolean keepAtomic) {
		this.mode = mode;
		vimBar.showMode(mode);
		if (mode == MODE_INSERT) {
			if (!keepAtomic) {
				lastAtomicID = axed.createAtomicID();
				usedAtomicID = false;
			} else {
				usedAtomicID = true;
			}
		}
	}
	
	/**
	 * switches mode, creates a new atomicID when switching
	 * into insert mode.
	 */
	private void switchMode(int mode) {
		this.mode = mode;
		vimBar.showMode(mode);
		if (mode == MODE_INSERT) {
			lastAtomicID = axed.createAtomicID();
		}
	}

	/**
	 * Handles events from the axedarea.
	 */
	public void onAxedEvent(AxedEvent event) {
		if (cmdMode != 0 && event.type == event.MOUSE_DOWN) {
			// ignore mousedown events when entering a command
			return;
		}
		super.onAxedEvent(event);
		switch (event.type) {
		case AxedEvent.KEY_PRESS:
			{
				AxedEvent.KeyPress ev = event.getKeyPress();
				if (ev.ctrlKey && ev.keycode == '1') {
					Window.alert("This is Axed version " + AxedArea.VERSION);
					return;
				}
				if (mode == MODE_INSERT) {
					keyPressInsert(ev);
				} else {
					keyPress(ev);
				}
			}
		}
	}

	/**
	 * Handles keypress in insert mode
	 */
	private void keyPressInsert(AxedEvent.KeyPress ev) {
		if (ev.ctrlKey) {
			switch (ev.keycode) {
			case 'v' :
			case 'V' :
				axed.inputCatcher.paste = true;
				usedAtomicID = true;
				break;
			case 'c' :
			case 'C' :
				axed.inputCatcher.setText(getSelection());
				axed.inputCatcher.selectAll();
				break;
			case 'x' :
			case 'X' :
				axed.inputCatcher.setText(getSelection());
				axed.inputCatcher.selectAll();
				axed.cut();
				usedAtomicID = true;
				break;
			case -InputCatcher.KEY_LEFT_ARROW:
				cursor.movePrevWord(1, false, false);
				break;
			case -InputCatcher.KEY_RIGHT_ARROW:
				cursor.moveNextWord(1, false, false);
				break;
			default :
				return;
			}
		}

		if (ev.keycode < 0) {
			switch (ev.keycode) {
			case         -InputCatcher.KEY_TAB : keyTab(); return;
			case  -InputCatcher.KEY_BACKSPACE  : keyBackspace(); return; 
			case     -InputCatcher.KEY_RETURN  : keyReturn();   return; 
			case     -InputCatcher.KEY_PAGEUP  : keyPageUp();    return;
			case   -InputCatcher.KEY_PAGEDOWN  : keyPageDown();  return;
			case        -InputCatcher.KEY_END  : keyEnd();   return; 
			case       -InputCatcher.KEY_POS1  : keyPos1(); return;
			case -InputCatcher.KEY_LEFT_ARROW  : keyLeft();      return;
			case   -InputCatcher.KEY_UP_ARROW  : keyUp();        return;
			case -InputCatcher.KEY_RIGHT_ARROW : keyRight();     return;
			case -InputCatcher.KEY_DOWN_ARROW  : keyDown();      return;
			case      -InputCatcher.KEY_INSERT : /*TODO*/        return;
			case         -InputCatcher.KEY_DEL : keyDel();    return;
			case         -InputCatcher.KEY_ESC : keyESC();  return;
			}
			return;
		}
		axed.insertChar(lastAtomicID, cursor, ev.ch);
		cursor.col++;
		cursor.retainLeft = -1;
		axed.moveCursor(cursor, AxedEvent.MOVETYPE_FOLLOWUP);
		usedAtomicID = true;
	}


	/**
	 * Handles keypress in normal mode
	 */
	public void keyPress(AxedEvent.KeyPress ev) {
		if (cmdMode != 0) {
			cmdBar.keyPress(ev);
			return;
		}

		if (ev.ctrlKey) {
			switch (ev.keycode) {
			case -InputCatcher.KEY_LEFT_ARROW  : keyCtrlLeft();  return;
			case -InputCatcher.KEY_RIGHT_ARROW : keyCtrlRight(); return;
			case                           'r' :
			case                           'R' : redo();      return;
			default : return;
			}
		}

		switch (ev.keycode) {
		case                           ':' : 
		case                           '/' : 
		case                           '?' : toCommandLine((char) ev.keycode); return;
		case                           '.' : keyDot(); return;
		case                           '0' :
		case                           '1' :
		case                           '2' :
		case                           '3' :
		case                           '4' :
		case                           '5' :
		case                           '6' :
		case                           '7' :
		case                           '8' :
		case                           '9' : digit(ev.keycode); return;
		case                           'a' : keyLowerA();    return;
		case                           'A' : keyUpperA();    return;
		case                           'c' : keyLowerC();    return;
		case                           'd' : keyLowerD();    return;
		case                           'D' : keyUpperD();    return;
		case                           'e' : keyLowerE();    return;
		case                           'E' : keyUpperE();    return;
		case                           'g' : keyLowerG();    return;
		case                           'G' : keyUpperG();    return;
		case                           'h' :                   
		case  -InputCatcher.KEY_LEFT_ARROW : keyLeft();      return;
		case   -InputCatcher.KEY_BACKSPACE : keyBackspace(); return;
		case                           '$' : keyDollar();    return;
		case         -InputCatcher.KEY_END : keyEnd();       return;
		case        -InputCatcher.KEY_POS1 : keyPos1();      return;
		case                           'i' : keyLowerI();    return;
		case                           'I' : keyUpperI();    return;
		case                           'j' : 
		case  -InputCatcher.KEY_DOWN_ARROW : keyDown();      return;
		case                           'J' : keyUpperJ();    return;
		case                           'k' : 
		case    -InputCatcher.KEY_UP_ARROW : keyUp();        return;
		case                           'l' :                    
		case -InputCatcher.KEY_RIGHT_ARROW : keyRight();     return;
		case     -InputCatcher.KEY_RETURN  : keyReturn();    return; 
		case      -InputCatcher.KEY_PAGEUP : keyPageUp();    return;
		case    -InputCatcher.KEY_PAGEDOWN : keyPageDown();  return;
		case                           'n' : keyLowerN();    return;
		case                           'o' : keyLowerO();    return;
		case                           'p' : keyLowerP();    return;
		case                           'P' : keyUpperP();    return;
		case                           's' : keyLowerS();    return;
		case                           't' : keyLowerT();    return;
		case                           'u' : keyLowerU();    return;
		case                           'U' : keyUpperU();    return;
		case                           'v' : keyLowerV();    return;
		case                           'V' : keyUpperV();    return;
		case                           'w' : keyLowerW();    return;
		case                           'W' : keyUpperW();    return;
		case         -InputCatcher.KEY_DEL :                 
		case                           'x' : keyLowerX();    return;
		case                           'X' : keyUpperX();    return;
		case                           'y' : keyLowerY();    return;
		case         -InputCatcher.KEY_ESC : keyESC();       return;
		case                           '>' : keyGreaterThan(); return;
		case                           '<' : keyLesserThan(); return;
		default                            : return;
		}
	}
}
